/*
 * Copyright (c) 2016, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

//*****************************************************************************
//
//! \addtogroup ap_sc_provisioning
//! @{
//
//*****************************************************************************

/* standard includes */
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

/* driverlib Header files */
#include <ti/devices/cc32xx/inc/hw_types.h>
#include <ti/devices/cc32xx/inc/hw_memmap.h>
#include <ti/devices/cc32xx/driverlib/rom.h>
#include <ti/devices/cc32xx/driverlib/rom_map.h>
#include <ti/devices/cc32xx/driverlib/prcm.h>
#include <ti/devices/cc32xx/driverlib/timer.h>

/* TI-DRIVERS Header files */
#include <Board.h>
#include <ti/drivers/GPIO.h>
#include <ti/display/Display.h>
#include <ti/drivers/net/wifi/simplelink.h>
#include <ti/drivers/net/wifi/wlan.h>

/* Application Header files */
#include "provisioning_task.h"
#include "platform.h"
#include <pthread.h>
#include <time.h>
#include "wifi_doorlock_app.h"

#define LED_TOGGLE_CONFIGURATION_TIMEOUT	(1000)	/* In msecs */
#define LED_TOGGLE_CONFIRMATION_TIMEOUT     (500)   /* In msecs */
#define LED_TOGGLE_CONNECTION_TIMEOUT       (250)   /* In msecs */

#define PROVISIONING_INACTIVITY_TIMEOUT		(600)	/* Provisioning inactivity timeout in seconds */

#define ROLE_SELECTION_BY_SL     			(0xFF)

/*!
 *  \brief  Provisioning modes
 */
typedef enum
{
    PrvsnMode_AP,       /* AP provisioning (AP role) */
    PrvsnMode_SC,       /* Smart Config provisioning (STA role) */
    PrvsnMode_APSC      /* AP + Smart Config provisioning (AP role) */

}PrvsnMode;

/** By default, setting the provisioning mode to AP + Smart Config.
  * Other values could be PrvsnMode_SC or PrvsnMode_AP
  */
#define PROVISIONING_MODE   PrvsnMode_APSC

/*!
 *  \brief  Provisioning status
 */
typedef enum
{
    PrvsnStatus_Stopped,
    PrvsnStatus_InProgress

}PrvsnStatus;

/*
 *  \brief  Application state's context
 */
typedef struct	_Provisioning_AppContext_t_
{
    PrvnState currentState; /* Current state of provisioning */
    uint32_t pendingEvents; /* Events pending to be processed */

    uint8_t role; /* SimpleLink's role - STATION/AP/P2P */

    uint8_t defaultRole; /* SimpleLink's default role, try not to change this */
    PrvsnMode provisioningMode; /* Provisioning Mode */
    PrvsnStatus provisioningStatus; /* */

    uint32_t asyncEvtTimeout; /* Timeout value*/

    uint32_t ledToggleTimeout; /* Timeout value */
}Provisioning_AppContext;

/*!
 *  \brief  Entry in the lookup table
 */
typedef struct
{
    fptr_EventHandler   p_evtHndl;  /* Pointer to the event handler */
    PrvnState          nextState;  /* Next state of provisioning */
}s_TblEntry;

/****************************************************************************************************************
                   GLOBAL VARIABLES
****************************************************************************************************************/
/* Overall application control block */
extern Application_CB App_CB;
extern Display_Handle display;

extern int32_t ConfigureSimpleLinkToDefaultState(SlWlanMode_e role);

/****************************************************************************
                      LOCAL FUNCTION PROTOTYPES
****************************************************************************/

//*****************************************************************************
//
//! \brief This function initializes provisioning process
//!
//! \param[in]  None
//!
//! \return None
//!
//****************************************************************************
void provisioningInit(void);

//*****************************************************************************
//
//! \brief This function starts provisioning process
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
int32_t provisioningStart(void);

//*****************************************************************************
//
//! \brief This function starts the SimpleLink in the configured role. 
//!		 The device notifies the host asynchronously when the initialization is completed
//!
//! \param[in]  role	Device shall be configured in this role
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
static int32_t InitSimplelink(uint8_t const role);

//*****************************************************************************
//
//! \brief This function handles 'APP_EVENT_STARTED' event
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
static int32_t HandleStrtdEvt(void);

//*****************************************************************************
//
//! \brief This function starts the async-event timer
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
static int32_t StartAsyncEvtTimer(uint32_t timeout);

 //*****************************************************************************
//
//! \brief This function stops the async-event timer
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
static int32_t StopAsyncEvtTimer(void);

//*****************************************************************************
//
//! \brief internal error detection during provisioning process
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
static int32_t ReportError(void);

//*****************************************************************************
//
//! \brief internal report current state during provisioning process
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
static int32_t ReportSM(void);

//*****************************************************************************
//
//! \brief steps following a successful provisioning process
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
static int32_t ReportSuccess(void);

//*****************************************************************************
//
//! \brief wait for connection following a successful provisioning process
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
static int32_t WaitForConn(void);

/****************************************************************************
                      GLOBAL VARIABLES
****************************************************************************/
/*!
 *  \brief  Application state's context
 */
Provisioning_AppContext gAppCtx;

timer_t gProvAsyncEventTimer;
timer_t gLedTimer;

/*!
 *  \brief   Application lookup/transition table
 */
const s_TblEntry gProvisioningTransitionTable[PrvnState_Max][PrvnEvent_Max] =
{
	/* PrvnState_Init */
	{
		/* Event: PrvnEvent_Triggered */
		{provisioningStart      , PrvnState_Idle			            },
        /* Event: PrvnEvent_Started */
        {ReportError         	, PrvnState_Error    					},
		/* Event: PrvnEvent_StartFailed */
        {ReportError         	, PrvnState_Error				    	},
		/* Event: PrvnEvent_ConfirmationSuccess */
        {ReportSM            , PrvnState_Completed                      },		/* special case where confirmation is received after application is restarted and NWP is still in provisioning */
        																		/* in this case, need to move to COMPLETED state */
		/* Event: PrvnEvent_ConfirmationFailed */
        {ReportError            , PrvnState_Error                      },
		/* Event: PrvnEvent_Stopped */
        {ReportSM            , PrvnState_Init		                },		/* in case of auto provisioning */
		/* Event: PrvnEvent_WaitForConn */
		{ReportSM         			, PrvnState_Error                      	},
        /* Event: PrvnEvent_Timeout */
        {ReportError            , PrvnState_Error		               	},
        /* Event: PrvnEvent_Error */
        {ReportError       		, PrvnState_Error                    	},
    },
    /* PrvnState_Idle */
    {
		/* Event: PrvnEvent_Triggered */
		{ReportError      , PrvnState_Error			            },
        /* Event: PrvnEvent_Started */
        {HandleStrtdEvt         , PrvnState_WaitForConfirmation    	},
		/* Event: PrvnEvent_StartFailed */
        {ReportError         	, PrvnState_Error				    	},
		/* Event: PrvnEvent_ConfirmationSuccess */
        {ReportError            , PrvnState_Error                      },
		/* Event: PrvnEvent_ConfirmationFailed */
        {ReportError            , PrvnState_Error                      },
		/* Event: PrvnEvent_Stopped */
        {ReportSM            	, PrvnState_Init                      	},
		/* Event: PrvnEvent_WaitForConn */
		{ReportSM         			, PrvnState_Error                      	},
        /* Event: PrvnEvent_Timeout */
        {ReportError            , PrvnState_Error		               	},
        /* Event: PrvnEvent_Error */
        {ReportError       		, PrvnState_Error                    	},
    },
	/* PrvnState_WaitForConfirmation */
	{
		/* Event: PrvnEvent_Triggered */
		{ReportError      , PrvnState_Error			            },
		/* Event: PrvnEvent_Started */
		{ReportError         	, PrvnState_Error    					},
		/* Event: PrvnEvent_StartFailed */
		{ReportSM         	, PrvnState_Idle				    	},
		/* Event: PrvnEvent_ConfirmationSuccess */
		{ReportSM            	, PrvnState_Completed                  },
		/* Event: PrvnEvent_ConfirmationFailed */
		{ReportSM            	, PrvnState_WaitForConfirmation     },
		/* Event: PrvnEvent_Stopped */
		{ReportSM            	, PrvnState_Init                       },
		/* Event: PrvnEvent_WaitForConn */
		{ReportSM         			, PrvnState_Error                      	},
		/* Event: PrvnEvent_Timeout */
		{ReportError            , PrvnState_Error		               	},
		/* Event: PrvnEvent_Error */
		{ReportError       		, PrvnState_Error                    	},
	},
	/* PrvnState_Completed */
	{
		/* Event: PrvnEvent_Triggered */
		{ReportError      , PrvnState_Error			            },
		/* Event: PrvnEvent_Started */
		{ReportError         	, PrvnState_Error    					},
		/* Event: PrvnEvent_StartFailed */
		{ReportError         	, PrvnState_Error				    	},
		/* Event: PrvnEvent_ConfirmationSuccess */
		{ReportError            , PrvnState_Error                  	},
		/* Event: PrvnEvent_ConfirmationFailed */
		{ReportError            , PrvnState_Error					    },
		/* Event: PrvnEvent_Stopped */
		{ReportSuccess         	, PrvnState_Init                      	},
		/* Event: PrvnEvent_WaitForConn */
		{WaitForConn         	, PrvnState_Init                      	},	/* this state should cover cases where feedback failed but profile exists */
		/* Event: PrvnEvent_Timeout */
		{ReportError            , PrvnState_Error		               	},
		/* Event: PrvnEvent_Error */
		{ReportError       		, PrvnState_Error                    	},
	},
	/* PrvnState_Error */
	{
		/* Event: PrvnEvent_Triggered */
		{ReportError      , PrvnState_Error			            },
		/* Event: PrvnEvent_Started */
		{ReportError         	  , PrvnState_Error    				},
		/* Event: PrvnEvent_StartFailed */
		{ReportError         	  , PrvnState_Error				   	},
		/* Event: PrvnEvent_ConfirmationSuccess */
		{ReportError              , PrvnState_Error                  	},
		/* Event: PrvnEvent_ConfirmationFailed */
		{ReportError              , PrvnState_Error				    },
		/* Event: PrvnEvent_Stopped */
		{ReportError              , PrvnState_Error                   	},
		/* Event: PrvnEvent_WaitForConn */
		{ReportSM         			, PrvnState_Error                      	},
		/* Event: PrvnEvent_Timeout */
		{ReportError              , PrvnState_Error		           	},
		/* Event: PrvnEvent_Error */
		{ReportError       		  , PrvnState_Error                  	},
	}
};

/*****************************************************************************
                  Callback Functions
*****************************************************************************/

//*****************************************************************************
//
//! \brief The device init callback
//!
//! \param[in]  status	Mode the device is configured in
//!
//! \return None
//!
//****************************************************************************
void SimpleLinkInitCallback(uint32_t status, SlDeviceInitInfo_t *DeviceInitInfo)
{
    Provisioning_AppContext *const pCtx = &gAppCtx;

    Display_printf(display, 0, 0,"[Provisioning task] Device started in %s role", (0 == status) ? "Station" :\
                                               ((2 == status) ? "AP" : "P2P"));

    SET_STATUS_BIT(App_CB.status, AppStatusBits_NwpInit);

    /** While provisioning is ongoing, the appropriate role is choosen by the
      * device itself, and host can remain agnostic to these details
      */
    if(pCtx->role == ROLE_SELECTION_BY_SL)
    {
        SignalProvisioningEvent(PrvnEvent_Started);
    }
    else
    {
        /* Either trigger an error/started event here */
        if(pCtx->role == status)
        {
            SignalProvisioningEvent(PrvnEvent_Started);
        }
        else
        {
            Display_printf(display, 0, 0,"[Provisioning task] But the intended role is %s",\
                                        (0 == pCtx->role) ? "Station" :\
                                        ((2 == pCtx->role) ? "AP" : "P2P"));

            SignalProvisioningEvent(PrvnEvent_Error);
        }
    }
}

//*****************************************************************************
//
//! \brief The interrupt handler for the async-evt timer
//!
//! \param[in]  None
//!
//! \return None
//!
//****************************************************************************
void ProvAsyncEvtTimerIntHandler(sigval val)
{
	/* Clear the timer interrupt. */
	Platform_TimerInterruptClear();

    /* One Shot */
    /* TBD - Check role for One-shot/periodic */
    StopAsyncEvtTimer();
    SignalProvisioningEvent(PrvnEvent_Timeout);
}

//*****************************************************************************
//
//! \brief The interrupt handler for the LED timer
//!
//! \param[in]  None
//!
//! \return None
//!
//****************************************************************************
void LedTimerIntHandler(sigval val)
{
	/* Clear the timer interrupt. */
	Platform_TimerInterruptClear();

	GPIO_toggle(Board_LED0);
}

//*****************************************************************************
//                 Local Functions
//*****************************************************************************
//*****************************************************************************
//
//! \brief Get AP security parameters
//!
//! \param  None
//!
//! \return None
//!
//*****************************************************************************
int32_t GetSecureStatus(void)
{
    uint8_t sec_type;
    uint16_t len = 1;
    uint16_t config_opt = SL_WLAN_AP_OPT_SECURITY_TYPE;

    /* Check security type when in AP mode */
    sl_WlanGet(SL_WLAN_CFG_AP_ID, &config_opt, &len, (_u8*) &sec_type);
    if (sec_type == SEC_TYPE_AP_MODE)
    {
        return 1;
    }
    return 0;
}

//*****************************************************************************
//
//! \brief Set AP security parameters
//!
//! \param  None
//!
//! \return None
//!
//*****************************************************************************
int32_t SetSecuredAP(const uint8_t sec_en)
{
    //set secured AP parameters
    uint8_t val = SEC_TYPE_AP_MODE;
    uint8_t open = SL_WLAN_SEC_TYPE_OPEN;
    uint8_t password[65];
    uint16_t len = strlen((char *) PASSWORD_AP_MODE);
    if (sec_en)
    {
        sl_WlanSet(SL_WLAN_CFG_AP_ID, SL_WLAN_AP_OPT_SECURITY_TYPE, 1,
                   (uint8_t *) &val);
        memset(password, 0, sizeof(password));
        memcpy(password, (char *) PASSWORD_AP_MODE, len);
        sl_WlanSet(SL_WLAN_CFG_AP_ID, SL_WLAN_AP_OPT_PASSWORD, len,
                   (uint8_t *) password);
        Display_printf(display, 0, 0, " Setting AP secured parameters");
    }
    else
    {
        sl_WlanSet(SL_WLAN_CFG_AP_ID, SL_WLAN_AP_OPT_SECURITY_TYPE, 1,
                   (uint8_t *) &open);
    }
    return 0;
}

//*****************************************************************************
//
//! \brief  This function configures the HTTPS server
//!
//! \param  None
//!
//! \return NetApp error codes or 0 upon success.
//!
//*****************************************************************************
int32_t ConfigureHttpsServer(void)
{
    int32_t retVal = 0;
    uint8_t httpsPort[] = { 0xBB, 0x01 }; /* 0x1BB = 443 */
    uint8_t secondaryPort[] = { 0x50, 0x00 }; /* 0x050 = 80 */
    uint8_t secondaryPortEnable[] = { 0x1 };
    uint8_t securityMode = 1;

    Display_printf(
            display, 0, 0,
            "[Provisioning task] ConfigureHttpsServer for secured mode...");

    retVal = sl_NetAppSet(SL_NETAPP_HTTP_SERVER_ID,
                          SL_NETAPP_HTTP_DEVICE_CERTIFICATE_FILENAME,
                          strlen((char *) SSL_SERVER_CERT),
                          (const uint8_t *) SSL_SERVER_CERT);
    if (retVal != 0)
    {
        Display_printf(display, 0, 0, "HTTP setting error: %d, option: %d",
                       retVal, SL_NETAPP_HTTP_DEVICE_CERTIFICATE_FILENAME);
    }

    retVal = sl_NetAppSet(SL_NETAPP_HTTP_SERVER_ID,
                          SL_NETAPP_HTTP_PRIVATE_KEY_FILENAME,
                          strlen((char *) SSL_SERVER_KEY),
                          (const uint8_t *) SSL_SERVER_KEY);
    if (retVal != 0)
    {
        Display_printf(display, 0, 0, "HTTP setting error: %d, option: %d",
                       retVal, SL_NETAPP_HTTP_PRIVATE_KEY_FILENAME);
    }

    retVal = sl_NetAppSet(SL_NETAPP_HTTP_SERVER_ID,
                          SL_NETAPP_HTTP_PRIMARY_PORT_SECURITY_MODE,
                          sizeof(securityMode), &securityMode);
    if (retVal != 0)
    {
        Display_printf(display, 0, 0, "HTTP setting error: %d, option: %d",
                       retVal, SL_NETAPP_HTTP_PRIMARY_PORT_SECURITY_MODE);
    }

    retVal = sl_NetAppSet(SL_NETAPP_HTTP_SERVER_ID,
                          SL_NETAPP_HTTP_PRIMARY_PORT_NUMBER, sizeof(httpsPort),
                          httpsPort);
    if (retVal != 0)
    {
        Display_printf(display, 0, 0, "HTTP setting error: %d, option: %d",
                       retVal, SL_NETAPP_HTTP_PRIMARY_PORT_NUMBER);
    }

    retVal = sl_NetAppSet(SL_NETAPP_HTTP_SERVER_ID,
                          SL_NETAPP_HTTP_SECONDARY_PORT_NUMBER,
                          sizeof(secondaryPort), secondaryPort);
    if (retVal != 0)
    {
        Display_printf(display, 0, 0, "HTTP setting error: %d, option: %d",
                       retVal, SL_NETAPP_HTTP_SECONDARY_PORT_NUMBER);
    }

    retVal = sl_NetAppSet(SL_NETAPP_HTTP_SERVER_ID,
                          SL_NETAPP_HTTP_SECONDARY_PORT_ENABLE,
                          sizeof(secondaryPortEnable), secondaryPortEnable);
    if (retVal != 0)
    {
        Display_printf(display, 0, 0, "HTTP setting error: %d, option: %d",
                       retVal, SL_NETAPP_HTTP_SECONDARY_PORT_ENABLE);
    }

    if (retVal >= 0)
    {
        retVal = sl_NetAppStop(SL_NETAPP_HTTP_SERVER_ID);
        Display_printf(display, 0, 0,
                       "[Provisioning task] HTTP Server Stopped");

        retVal = sl_NetAppStart(SL_NETAPP_HTTP_SERVER_ID);
        if (retVal == SL_ERROR_HTTP_SERVER_ENABLE_FAILED)
        {
            do
            {
                sleep(1);
                retVal = sl_NetAppStart(SL_NETAPP_HTTP_SERVER_ID);
            }
            while (retVal == SL_ERROR_HTTP_SERVER_ENABLE_FAILED);
        }

        Display_printf(display, 0, 0,
                       "[Provisioning task] HTTP Server Re-started");
    }
    return retVal;
}

//*****************************************************************************
//
//! \brief This function initializes provisioning process
//!
//! \param[in]  None
//!
//! \return None
//!
//****************************************************************************
void provisioningInit(void)
{
    Provisioning_AppContext * const pCtx = &gAppCtx;

    Platform_TimerInit(ProvAsyncEvtTimerIntHandler, &gProvAsyncEventTimer);

    /** By default, setting the provisioning mode to AP + Smart Config.
     * Other values could be PrvsnMode_SC or PrvsnMode_AP
     */
    pCtx->provisioningMode = PROVISIONING_MODE;
    switch (pCtx->provisioningMode)
    {
    case PrvsnMode_APSC:
        pCtx->defaultRole = ROLE_AP;
        break;

    case PrvsnMode_AP:
        pCtx->defaultRole = ROLE_AP;
        break;

    case PrvsnMode_SC:
        pCtx->defaultRole = ROLE_STA;
        break;
    }

    /* Provisioning has not started yet */
    pCtx->provisioningStatus = PrvsnStatus_Stopped;
    pCtx->currentState = PrvnState_Init;
}

//*****************************************************************************
//
//! \brief This function starts provisioning process
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
int32_t provisioningStart(void)
{
    int32_t retVal = 0;
    Provisioning_AppContext * const pCtx = &gAppCtx;
    SlDeviceVersion_t ver = { 0 };
    uint8_t configOpt = 0;
    uint16_t configLen = 0;

//    /* Start network processor if it is not on                                        */
//    if (!IS_NW_PROCSR_ON(App_CB.status))
//    {
//        /* Start the network processor */
//        retVal = sl_Start(0, 0, 0);
//        if (retVal < 0)
//        {
//            Display_printf(
//                    display,
//                    0,
//                    0,
//                    "\t [Provisioning task] Error - Failed to start network processor");
//            return retVal;
//        }
//        else
//        {
//            SET_STATUS_BIT(App_CB.status, AppStatusBits_NwpInit);
//        }
//    }

    /* Set HTTPS enabled and secured AP */
    if (HTTPS_ENABLE)
    {
        ConfigureHttpsServer();
    }
    retVal = GetSecureStatus();
    if ((SECURED_AP_ENABLE) || (retVal != SECURED_AP_ENABLE))
    /*Will enter only if previous and present Secure AP are different or Secure AP is 1*/
    {
        SetSecuredAP(SECURED_AP_ENABLE);
    }

    /* check if provisioning is running */
    /* if auto provisioning - the command stops it automatically */
    /* in case host triggered provisioning - need to stop it explicitly */
    configOpt = SL_DEVICE_GENERAL_VERSION;
    configLen = sizeof(ver);
    retVal = sl_DeviceGet(SL_DEVICE_GENERAL, &configOpt, &configLen,
                          (uint8_t *) (&ver));
    if (SL_RET_CODE_PROVISIONING_IN_PROGRESS == retVal)
    {
        Display_printf(
                display,
                0,
                0,
                "[Provisioning task] Provisioning is already running, stopping it...");
        retVal = sl_WlanProvisioning(SL_WLAN_PROVISIONING_CMD_STOP, ROLE_STA, 0,
                                     NULL, 0);

        /* return  SL_RET_CODE_PROVISIONING_IN_PROGRESS to indicate the SM to stay in the same state*/
        return SL_RET_CODE_PROVISIONING_IN_PROGRESS;
    }

    /*
     IMPORTANT NOTE - This is an example reset function, user must update
     this function to match the application settings.
     */
    retVal = ConfigureSimpleLinkToDefaultState(ROLE_AP);

    if (retVal < 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Provisioning task] Failed to configure the device in its default state");
        return retVal;
    }

    retVal = sl_Stop(SL_STOP_TIMEOUT);

    if(retVal < 0)
    {
        Display_printf(
                display,
                0,
                0,
                "\t [Provisioning task] Failed to stop network processor while configuring to default state");
        return retVal;
    }

    Display_printf(display, 0, 0,
                   "[Provisioning task] Device is configured in default state");

    /* Provisioning has not started yet */
    pCtx->provisioningStatus = PrvsnStatus_Stopped;

    retVal = InitSimplelink(pCtx->defaultRole);
    if (retVal < 0)
    {
        Display_printf(
                display, 0, 0,
                "\t [Provisioning task] Failed to initialize the device");
        return retVal;
    }

    return retVal;
}

//*****************************************************************************
//
//! \brief This function starts the SimpleLink in the configured role. 
//!		 The device notifies the host asynchronously when the initialization is completed
//!
//! \param[in]  role	Device shall be configured in this role
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
static int32_t InitSimplelink(uint8_t const role)
{
    Provisioning_AppContext * const pCtx = &gAppCtx;
    int32_t retVal = -1;

    pCtx->role = role;
    pCtx->pendingEvents = 0;

    retVal = sl_Start(0, 0, (P_INIT_CALLBACK) SimpleLinkInitCallback);
    ASSERT_ON_ERROR(retVal);

    /* Start timer */
    pCtx->asyncEvtTimeout = ASYNC_EVT_TIMEOUT;
    retVal = StartAsyncEvtTimer(pCtx->asyncEvtTimeout);
    ASSERT_ON_ERROR(retVal);

    return retVal;
}

//*****************************************************************************
//
//! \brief This function handles 'APP_EVENT_STARTED' event
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************

static int32_t HandleStrtdEvt(void)
{
    Provisioning_AppContext * const pCtx = &gAppCtx;
    int32_t retVal = 0;

    /** If provisioning has already started, don't do anything here
     * The state-machine shall keep waiting for the provisioning status
     */
    if (PrvsnStatus_Stopped == pCtx->provisioningStatus)
    {
        /* Start provisioning process */
        Display_printf(display, 0, 0,
                       "[Provisioning task] Starting Provisioning - ");
        Display_printf(
                display, 0, 0,
                "[Provisioning task] in mode %d (0 = AP, 1 = SC, 2 = AP+SC)",
                pCtx->provisioningMode);

        retVal = sl_WlanProvisioning(pCtx->provisioningMode, ROLE_STA,
        PROVISIONING_INACTIVITY_TIMEOUT,
                                     NULL, 0);
        ASSERT_ON_ERROR(retVal);

        pCtx->provisioningStatus = PrvsnStatus_InProgress;
        Display_printf(
                display,
                0,
                0,
                "[Provisioning task] Provisioning Started. Waiting to be provisioned..!!");
    }

    return retVal;
}


//*****************************************************************************
//
//! \brief This function starts the async-event timer
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
static int32_t StartAsyncEvtTimer(uint32_t timeout)
{
    Provisioning_AppContext * const pCtx = &gAppCtx;

    pCtx->asyncEvtTimeout = timeout;
    Platform_TimerStart(pCtx->asyncEvtTimeout, gProvAsyncEventTimer, 0);
    return 0;
}

//*****************************************************************************
//
//! \brief This function stops the async-event timer
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
static int32_t StopAsyncEvtTimer(void)
{
    Provisioning_AppContext * const pCtx = &gAppCtx;

    if (0 != pCtx->asyncEvtTimeout)
    {
        Platform_TimerStop(gProvAsyncEventTimer);
        pCtx->asyncEvtTimeout = 0;
    }

    return 0;
}


//*****************************************************************************
//
//! \brief internal error detection during provisioning process
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
static int32_t ReportError(void)
{
    Provisioning_AppContext * const pCtx = &gAppCtx;
    unsigned char event;
    struct mq_attr provSignalQueueAttrs;

    mq_getattr(App_CB.provSignalQueue, &provSignalQueueAttrs);
    if (provSignalQueueAttrs.mq_curmsgs > 0)
    {
        mq_receive(App_CB.provSignalQueue, (char *) &event, 1, NULL);
    }

    Display_printf(display, 0, 0,
                   "[Provisioning task] Unexpected SM: State = %d, Event = %d",
                   pCtx->currentState, event);
    return -1; 
}

//*****************************************************************************
//
//! \brief internal report current state during provisioning process
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
static int32_t ReportSM(void)
{
    unsigned char event;
    struct mq_attr provSignalQueueAttrs;

    mq_getattr(App_CB.provSignalQueue, &provSignalQueueAttrs);
    if (provSignalQueueAttrs.mq_curmsgs > 0)
    {
        mq_receive(App_CB.provSignalQueue, (char *) &event, 1, NULL);
    }

    return 0;
}

//*****************************************************************************
//
//! \brief steps following a successful provisioning process
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
static int32_t ReportSuccess(void)
{
    Provisioning_AppContext * const pCtx = &gAppCtx;
    uint16_t ConfigOpt;
    uint16_t ipLen;
    SlNetCfgIpV4Args_t ipV4 = { 0 };
    int32_t retVal;

    struct networkIfMsg networkMsg;

    Display_printf(
            display, 0, 0,
            "[Provisioning task] Provisioning completed successfully..!");
    pCtx->provisioningStatus = PrvsnStatus_Stopped;
    StopLedEvtTimer();

    /* Get the device's IP address */
    ipLen = sizeof(SlNetCfgIpV4Args_t);
    ConfigOpt = 0;
    retVal = sl_NetCfgGet(SL_NETCFG_IPV4_STA_ADDR_MODE, &ConfigOpt, &ipLen,
                          (uint8_t *) &ipV4);
    if (retVal == 0)
    {
        Display_printf(display, 0, 0,
                       "[Provisioning task] IP address is %d.%d.%d.%d",
                       SL_IPV4_BYTE(ipV4.Ip, 3), SL_IPV4_BYTE(ipV4.Ip, 2),
                       SL_IPV4_BYTE(ipV4.Ip, 1), SL_IPV4_BYTE(ipV4.Ip, 0));
    }

    GPIO_write(Board_LED0, Board_LED_ON);

    /* signal to linklocal task */
    networkMsg.msg = NETWORK_MSG_APSC_PROV_DONE;
    mq_send(App_CB.networkIfQueue, (char *)&networkMsg, sizeof(struct networkIfMsg), NULL);


    return 0;
}


//*****************************************************************************
//
//! \brief wait for connection following a successful provisioning process
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
static int32_t WaitForConn(void)
{
    Provisioning_AppContext * const pCtx = &gAppCtx;
    struct timespec ts;
    int32_t retVal;

    while (((!IS_IPV6L_ACQUIRED(App_CB.status)
            || !IS_IPV6G_ACQUIRED(App_CB.status))
            && !IS_IP_ACQUIRED(App_CB.status)) || !IS_CONNECTED(App_CB.status))
    {
        clock_gettime(CLOCK_REALTIME, &ts);
        ts.tv_sec += PROFILE_ASYNC_EVT_TIMEOUT;

        retVal = sem_timedwait(&App_CB.connectionAsyncEvent, &ts);
        if ((retVal == 116) || (retVal == -1)) /* freertos return -1 in case of timeout */
        {
            Display_printf(
                    display,
                    0,
                    0,
                    "[Provisioning task] Cannot connect to AP or profile does not exist");
            GPIO_write(Board_LED0, Board_LED_OFF);
            pCtx->currentState = PrvnState_Init; /* this state is set so that PrvnEvent_Triggered would invoke provisioning again */
            SignalProvisioningEvent(PrvnEvent_Triggered);

            return 0;
        }
    }

    Display_printf(display, 0, 0,
                   "[Provisioning task] Connection to AP succeeded");

    return ReportSuccess();
}

//****************************************************************************
//                            MAIN FUNCTION
//****************************************************************************

//*****************************************************************************
//
//! \brief  This function signals the application events
//!
//! \param  None
//!
//! \return None
//!
//*****************************************************************************
int16_t SignalProvisioningEvent(PrvnEvent event)
{
    char msg = (char) event;

    //
    //signal provisioning task about SL Event
    //
    mq_send(App_CB.provSignalQueue, &msg, 1, 0);

    return 0;
}

//*****************************************************************************
//
//! \brief This function gets the current provisioning state
//!
//! \param[in]  None
//!
//! \return provisioning state
//!
//****************************************************************************
PrvnState GetProvisioningState()
{
    Provisioning_AppContext * const pCtx = &gAppCtx;

    return pCtx->currentState;
}


//*****************************************************************************
//
//! \brief This function starts the led toggling timer
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
int32_t StartLedEvtTimer(uint32_t timeout)
{
    Provisioning_AppContext * const pCtx = &gAppCtx;

    pCtx->ledToggleTimeout = timeout;
    Platform_TimerStart(pCtx->ledToggleTimeout, gLedTimer, 1);

    return 0;
}

//*****************************************************************************
//
//! \brief This function stops the led toggling timer
//!
//! \param[in]  None
//!
//! \return 0 on success, negative value otherwise
//!
//****************************************************************************
int32_t StopLedEvtTimer(void)
{
    Provisioning_AppContext * const pCtx = &gAppCtx;

    if (0 != pCtx->ledToggleTimeout)
    {
        Platform_TimerStop(gLedTimer);
        pCtx->ledToggleTimeout = 0;
    }

    return 0;
}


//*****************************************************************************
//
//! \brief This function stops provisioning process
//!
//! \param[in]  None
//!
//! \return SL_RET_CODE_PROVISIONING_IN_PROGRESS if provisioning was running, otherwise 0
//!
//****************************************************************************
int32_t provisioningStop(void)
{
    Provisioning_AppContext * const pCtx = &gAppCtx;
    int32_t retVal;
    PrvnState provisioningState;
    SlDeviceVersion_t ver = { 0 };
    uint8_t configOpt = 0;
    uint16_t configLen = 0;

    /* check if provisioning is running */
    configOpt = SL_DEVICE_GENERAL_VERSION;
    configLen = sizeof(ver);
    retVal = sl_DeviceGet(SL_DEVICE_GENERAL, &configOpt, &configLen,
                          (uint8_t *) (&ver));
    if (SL_RET_CODE_PROVISIONING_IN_PROGRESS == retVal)
    {
        Display_printf(
                display,
                0,
                0,
                "[Provisioning task]  Provisioning is already running, stopping it...");
        retVal = sl_WlanProvisioning(SL_WLAN_PROVISIONING_CMD_STOP, ROLE_STA, 0,
                                     NULL, 0);

        /* wait for the stopped event to arrive - wait for PrvnState_Init */
        do
        {
            provisioningState = GetProvisioningState();
            usleep(1000);
        }
        while (provisioningState != PrvnState_Init);

        pCtx->provisioningStatus = PrvsnStatus_Stopped;

        retVal = SL_RET_CODE_PROVISIONING_IN_PROGRESS;
    }
    else if (retVal < 0)
    {
        return retVal;
    }
    else
    {
        retVal = 0;
    }

    return retVal;
}



//*****************************************************************************
//
//! \brief This is the main provisioning task
//!
//! \param[in]  None
//!
//! \return None
//!
//****************************************************************************
void * provisioningTask(void *pvParameters)
{
    Provisioning_AppContext * const pCtx = &gAppCtx;

    /* State machine vars */
    unsigned char event;
    s_TblEntry *pEntry = NULL;
    mq_attr attr;

    /* Create message queue */
    attr.mq_curmsgs = 0;
    attr.mq_flags = 0;
    attr.mq_maxmsg = 10;
    attr.mq_msgsize = sizeof(unsigned char);
    App_CB.provSignalQueue = mq_open("SMQueue", O_CREAT, 0, &attr);

    /* initialize one-time parameters for provisioning */
    provisioningInit();

    /* at this point, provisioning has not started yet, unless auto provisioning is running */
    /* in this case, if provisioning from mobile app is running, it would not be possible to send most of the commands to the device */
    /* need to stop provisioning */
    provisioningStop();

    sem_post(&App_CB.provReady);
    sem_wait(&App_CB.startBarrier);

    while (1)
    {
        mq_receive(App_CB.provSignalQueue, (char *) &event, 1, NULL);

        StopAsyncEvtTimer();

        /* Find Next event entry */
        pEntry =
                (s_TblEntry *) &gProvisioningTransitionTable[pCtx->currentState][event];

        if (NULL != pEntry->p_evtHndl)
        {
            if (pEntry->p_evtHndl() < 0)
            {
                Display_printf(display, 0, 0, "Event handler failed..!!");
                while (1);
            }
        }

        /* Change state acording to event */
        if (pEntry->nextState != pCtx->currentState)
        {
            pCtx->currentState = pEntry->nextState;
        }
    }
}
//*****************************************************************************
//
// Close the Doxygen group.
//! @}
//
//*****************************************************************************
